package com.bucket.cloud.gateway.locator;

import com.alibaba.fastjson.JSON;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.bucket.cloud.gateway.event.GatewayResourceRefreshEvent;
import com.bucket.cloud.gateway.repository.RedisRouteDefinitionRepository;
import com.bucket.cloud.gateway.service.feign.GatewayInterfaceServiceClient;
import com.bucket.cloud.system.api.model.SystemInterface;
import com.bucket.cloud.system.api.vo.SystemGatewayIplimitVO;
import com.bucket.cloud.system.api.vo.SystemInterfaceVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.support.NameUtils;
import org.springframework.context.ApplicationListener;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;

import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * 自定义动态权限加载器
 */
@Slf4j
public class ApiResourceLocator implements ApplicationListener<GatewayResourceRefreshEvent> {
    /**
     * 单位时间
     */
    /**
     * 1分钟
     */
    public static final long SECONDS_IN_MINUTE = 60;
    /**
     * 一小时
     */
    public static final long SECONDS_IN_HOUR = 3600;
    /**
     * 一天
     */
    public static final long SECONDS_IN_DAY = 24 * 3600;

    /**
     * 请求总时长
     */
    public static final int PERIOD_SECOND_TTL = 10;
    public static final int PERIOD_MINUTE_TTL = 2 * 60 + 10;
    public static final int PERIOD_HOUR_TTL = 2 * 3600 + 10;
    public static final int PERIOD_DAY_TTL = 2 * 3600 * 24 + 10;


    private List<SystemInterface> accessAuthorities;

    private List<SystemInterfaceVO> ipBlacks;

    private List<SystemInterfaceVO> ipWhites;

    /**
     * 缓存
     */
    private Map<String, List> cache = new HashMap<>();
    /**
     * 权限列表
     */
    private HashMap<String, Collection<ConfigAttribute>> allConfigAttributes;

    private GatewayInterfaceServiceClient gatewayInterfaceServiceClient;

    private RedisTemplate redisTemplate;

    public ApiResourceLocator() {
        allConfigAttributes = Maps.newHashMap();
        accessAuthorities = cache.put("accessAuthorities", new ArrayList<>());
        ipBlacks = cache.put("ipBlacks", new ArrayList<>());
        ipWhites = cache.put("ipWhites", new ArrayList<>());
    }

    public ApiResourceLocator(RedisTemplate redisTemplate, GatewayInterfaceServiceClient gatewayInterfaceServiceClient) {
        this();
        this.redisTemplate = redisTemplate;
        this.gatewayInterfaceServiceClient = gatewayInterfaceServiceClient;
    }

    /**
     * 清空缓存并刷新
     */
    public void refresh() {
        this.cache.clear();
        this.allConfigAttributes.clear();
        loadAuthority();
        loadIpBlackList();
        loadIpWhiteList();
    }

    @Override
    public void onApplicationEvent(GatewayResourceRefreshEvent event) {
        refresh();
    }

    /**
     * 获取路由后的地址
     *
     * @return
     */
    protected String getFullPath(String serviceId, String path) {
        return (String) redisTemplate.opsForHash().values(RedisRouteDefinitionRepository.GATEWAY_ROUTES)
                .stream()
                .filter(routeDefinition -> {
                    RouteDefinition rd = JSON.parseObject(routeDefinition.toString(), RouteDefinition.class);
                    return rd.getUri().toString().equals("lb://" + serviceId);
                })
                .map(routeDefinition -> {
                    RouteDefinition rd = JSON.parseObject(routeDefinition.toString(), RouteDefinition.class);
                    String fullPath = rd.getPredicates().stream().filter(predicateDefinition ->
                            ("Path").equalsIgnoreCase(predicateDefinition.getName())
                    ).findFirst().get().getArgs().get(NameUtils.GENERATED_NAME_PREFIX + "0").replace("/**", path);
                    return fullPath;
                }).findFirst().orElse(path);
        //@Todo 下面写法会出现http请求刷新时报错，跟flux有关，暂无办法解决
//        return routeDefinitionLocator.getRouteDefinitions()
//                .toStream()
//                .filter(routeDefinition -> routeDefinition.getUri().toString().equals("lb://" + serviceId))
//                .map(routeDefinition -> {
//                    String fullPath = routeDefinition.getPredicates().stream().filter(predicateDefinition ->
//                            ("Path").equalsIgnoreCase(predicateDefinition.getName())
//                    ).findFirst().get().getArgs().get(NameUtils.GENERATED_NAME_PREFIX + "0").replace("/**", path);
//                    return fullPath;
//                }).findFirst().orElse(path);
    }

    /**
     * @auther: cyq
     * @Description: 补全接口完全路径
     * @Date: 2020/6/11 16:55
     */
    protected String getInterfaceFullPath(String serviceId, String path) {
        serviceId = serviceId.startsWith("/") ? serviceId : "/" + serviceId;
        path = path.startsWith("/") ? path : "/" + path;
        return serviceId + path;
    }

    /**
     * 加载授权列表
     */
    public void loadAuthority() {
        allConfigAttributes = Maps.newHashMap();
        accessAuthorities = Lists.newArrayList();
        Collection<ConfigAttribute> array;
        ConfigAttribute cfg;
        try {
            // 查询所有接口
            accessAuthorities = gatewayInterfaceServiceClient.queryAllApi().getData();
            if (accessAuthorities != null) {
                for (SystemInterface item : accessAuthorities) {
                    String path = item.getIntUrl();
                    if (path == null) {
                        continue;
                    }
                    String fullPath = getInterfaceFullPath(item.getRoutId(), path);
                    item.setIntUrl(fullPath);
                    array = allConfigAttributes.get(fullPath);
                    if (array == null) {
                        array = new ArrayList<>();
                    }
                    if (!array.contains(item.getId())) {
                        cfg = new SecurityConfig(item.getId());
                        array.add(cfg);
                    }
                    allConfigAttributes.put(fullPath, array);
                }
                log.info("=============加载动态权限:{}==============", accessAuthorities.size());
            }
        } catch (Exception e) {
            e.printStackTrace();
            log.error("加载动态权限错误:{}", e.getMessage());
        }
    }

    /**
     * 加载IP黑名单
     */
    public void loadIpBlackList() {
        ipBlacks = Lists.newArrayList();
        try {
            SystemGatewayIplimitVO vo = new SystemGatewayIplimitVO();
            vo.setLimitType("2");
            vo.setState(1);
            ipBlacks = gatewayInterfaceServiceClient.queryLimitApiList(vo).getData();

            if (ipBlacks != null) {
                for (SystemInterfaceVO item : ipBlacks) {
                    item.setIpAddressSet(item.getIplimit().getLimitIp());
                    item.setIntUrl(getFullPath(item.getRoutId(), item.getIntUrl()));
                }
                log.info("=============加载IP黑名单:{}==============", ipBlacks.size());
            }
        } catch (Exception e) {
            log.error("加载IP黑名单错误:{}", e.getMessage());
        }
    }

    /**
     * 加载IP白名单
     */
    public void loadIpWhiteList() {
        ipWhites = Lists.newArrayList();
        try {
            SystemGatewayIplimitVO vo = new SystemGatewayIplimitVO();
            vo.setLimitType("1");
            vo.setState(1);
            ipWhites = gatewayInterfaceServiceClient.queryLimitApiList(vo).getData();
            if (ipWhites != null) {
                for (SystemInterfaceVO item : ipWhites) {
                    item.setIpAddressSet(item.getIplimit().getLimitIp());
                    item.setIntUrl(getFullPath(item.getRoutId(), item.getIntUrl()));
                }
                log.info("=============加载IP白名单:{}==============", ipWhites.size());
            }
        } catch (Exception e) {
            log.error("加载IP白名单错误:{}", e.getMessage());
        }
    }


    /**
     * 获取单位时间内刷新时长和请求总时长
     *
     * @param timeUnit
     * @return
     */
    private long[] getIntervalAndQuota(String timeUnit) {
        if (timeUnit.equals(TimeUnit.SECONDS.name())) {
            return new long[]{SECONDS_IN_MINUTE, PERIOD_SECOND_TTL};
        } else if (timeUnit.equals(TimeUnit.MINUTES.name())) {
            return new long[]{SECONDS_IN_MINUTE, PERIOD_MINUTE_TTL};
        } else if (timeUnit.equals(TimeUnit.HOURS.name())) {
            return new long[]{SECONDS_IN_HOUR, PERIOD_HOUR_TTL};
        } else if (timeUnit.equals(TimeUnit.DAYS.name())) {
            return new long[]{SECONDS_IN_DAY, PERIOD_DAY_TTL};
        } else {
            throw new IllegalArgumentException("Don't support this TimeUnit: " + timeUnit);
        }
    }


    public List<SystemInterface> getAccessAuthorities() {
        return accessAuthorities;
    }

    public void setAccessAuthorities(List<SystemInterface> accessAuthorities) {
        this.accessAuthorities = accessAuthorities;
    }

    public List<SystemInterfaceVO> getIpBlacks() {
        return ipBlacks;
    }

    public void setIpBlacks(List<SystemInterfaceVO> ipBlacks) {
        this.ipBlacks = ipBlacks;
    }

    public List<SystemInterfaceVO> getIpWhites() {
        return ipWhites;
    }

    public void setIpWhites(List<SystemInterfaceVO> ipWhites) {
        this.ipWhites = ipWhites;
    }

    public Map<String, List> getCache() {
        return cache;
    }

    public void setCache(Map<String, List> cache) {
        this.cache = cache;
    }

    public HashMap<String, Collection<ConfigAttribute>> getAllConfigAttributes() {
        return allConfigAttributes;
    }

    public void setAllConfigAttributes(HashMap<String, Collection<ConfigAttribute>> allConfigAttributes) {
        this.allConfigAttributes = allConfigAttributes;
    }
}